package libnetdb.dm.assoc.util;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * This class provide a Exception-Throwable interface for a
 * runnable task. In general, a <code>Task</code> should be
 * executed for exactly once. Running a task for the seconds time
 * will throws an <code>IlleageStateException</code> when calling
 * the function <code>run()</code>.
 *
 * @author Cheng-Ru Lin
 * @author Pin Ting
 * @version 2.1 2004/6
 */
abstract public class Task{

    /**
     * This state indicates that the task has not yet been
     * running.
     */
    public static final int INITIAL = 0;

    /**
     * This state indicates that the task is running currently.
     */
    public static final int RUNNING = 1;

    /**
     * This state indicates that the task is stopped for some
     * errors or the user cancel the task if this is an
     * controllable task. One can obtain the exception cause this
     * ERROR state by <code>getException()</code>.
     * If this error state is caused by user cancel command
     * then the exception will be <code>CanceledException</code>.
     *
     * @see #getException()
     */
    public static final int ERROR = 2;

    /**
     * The state indicates that the controlled task is
     * completed and stopeed.
     */
    public static final int STOP = 3;

    private int state = Task.INITIAL;
    //private Exception exception = null;
    private Throwable  exception = null;
    private ArrayList listeners = new ArrayList();
    private Thread worker = null;

    /**
     * Constructs a new allocated <code>Task</code>.
     */
    protected Task() {
    }

    /**
     * Sets the state of the task and notify all the listener of
     * this change.
     *
     * @param state the state after the change
     */
    protected void setState(int state){
        this.state = state;
        Iterator it = listeners.iterator();
        while(it.hasNext())
            ((StateListener)it.next()).stateChanged(this, state);
    }

    /**
     * Resets the state of the task to <code>Task.INITIAL</code>.
     *
     */
    protected void reset() {
        this.exception = null;
        this.worker = null;
        this.setState(Task.INITIAL);
    }


    /**
     * Starts to run the task. In general, this function should be
     * called only once for each instance of task. One should create
     * another task to run again.
     *
     * @throws Exception if any underlying exception has happened.
     */
    public final void run() throws Throwable{
        if(state != Task.INITIAL)
            throw new IllegalStateException("state = " + state);
        try{
            setState(Task.RUNNING);
            this.runImpl();
            setState(Task.STOP);
        }catch(Throwable e){
            System.out.println(e);
            setState(Task.ERROR);
            throw this.exception = e;
        }finally{
            synchronized(this){this.notifyAll();}
        }
    }

    /**
     * Gets the Exception causes the stop controlled task. Note
     * that it will return <code>null</code> if the controlled
     * task complete sucessfully.
     *
     * @return the exception cause the stop of the controlled task.
     */
    public Throwable getException(){
        return exception;
    }

    /**
     * Gets the state of this task.
     *
     * @return the task of the task
     */
    public int getState(){
        return state;
    }

    /**
     * Starts the task in a new thread.
     */
    public synchronized void start(){
        if(worker != null)
            throw new IllegalStateException("Task has been started");
        (worker = new Worker()).start();
        System.out.println("Task.start() executed.");
    }
    
    //Pin Ting, 2004/06/10
    public synchronized void start(ThreadGroup tg)
    {
        if(worker != null)
            throw new IllegalStateException("Task has been started");
        (worker = new Worker(tg, "workerthread")).start();
        System.out.println("Task.start(ThreadGroup) executed.");
    }

    /**
     * Adds the specified <code>TaskListener</code> into this task.
     *
     * @param listener the <code>TaskListener</code> to be added
     */
    public void addStateListener(StateListener listener){
        listeners.add(listener);
    }

    /**
     * Removes a specified<code>TaskListener</code> from this task.
     *
     * @param listener the <code>TaskListener</code> to be removed
     */
    public void removeStateListener(StateListener listener){
        listeners.remove(listener);
    }

    /**
     * Causes the current thread to wait until the controlled task
     * stop.
     *
     * @return the state of the controlled task when function returns
     * @throws InterruptedException if being interrupted by other thread
     */
    public synchronized int join() throws InterruptedException{
        while(state == RUNNING || state == INITIAL) wait();
        return state;
    }

    /**
     * Causes the current thread to wait until the controlled task
     * stop or a exceeding a specified timeout.
     *
     * @param timeout the maximal milli seconds to wait the task to stop
     * @return the state of the controlled task when function returns
     * @throws InterruptedException if being interrupted by other thread
     */
    public int join(long timeout) throws InterruptedException{
        long current = System.currentTimeMillis(),
                due = current + timeout;
        while(state == RUNNING && current <= due){
            wait(due - current);
            current = System.currentTimeMillis();
        }
        return state;
    }

    private class Worker extends Thread{
    	public Worker(){
    		super.setPriority(Thread.MIN_PRIORITY);
    	}
        public void run(){
            try{
                Task.this.run();
            }catch(Throwable e){}
        }
        
        //Pin Ting, 2004/06/10
        public Worker(ThreadGroup tg, String n)
        {
        	super(tg, n);
        	super.setPriority(Thread.MIN_PRIORITY);
        }
    }

    abstract protected void runImpl() throws Exception;
}
